<?php

class WSB_Model3
{

    public function __construct(array $properties = null)
    {
        if(is_array($properties)) {
            $this->setProperties($properties);
        }
    }

    public function __set($name, $value)
    {
        $method = 'set' . $name;
        if (!method_exists($this, $method)) {
            throw new Exception("Invalid property '$name'");
        }
        $this->$method($value);
    }

    public function __get($name)
    {
        $method = 'get' . $name;
        if (!method_exists($this, $method)) {
            throw new Exception("Invalid property '$name'");
        }
        return $this->$method();
    }

    public function __isset($name)
    {
        $method = 'get' . $name;
        if (method_exists($this, $method)) {
            return $this->$method() !== null;
        }
        return false;
    }

    public function __unset($name)
    {
        $method='set'.$name;
        if(method_exists($this,$method)) {
            $this->$method(null);
        }
    }

    public function setProperties(array $options)
    {
        $methods = get_class_methods($this);
        foreach ($options as $key => $value) {
            $method = 'set' . ucfirst($key);
            if (in_array($method, $methods)) {
                $this->$method($value);
            } else {
                throw new Exception("Invalid property '$key'");
            }
        }
        return $this;
    }

    public function getProperties()
    {
        $properties = array();
        foreach(get_class_methods($this) AS $method) {
            if(substr($method, 0, 3) === 'get') {
                $key = substr($method, 3);
                $key{0} = strtolower($key{0}); // workaround for function lcfirst
                if($key != 'properties') {
                    $properties[$key] = $this->$method();
                }
            }
        }
        return $properties;
    }

    /**
     * Determines whether a property is defined.
     * A property is defined if there is a getter or setter method
     * defined in the class. Note, property names are case-insensitive.
     * @param string the property name
     * @return boolean whether the property is defined
     * @see canGetProperty
     * @see canSetProperty
     */
    public function hasProperty($name)
    {
        return method_exists($this,'get'.$name) || method_exists($this,'set'.$name);
    }

    /**
     * Determines whether a property can be read.
     * A property can be read if the class has a getter method
     * for the property name. Note, property name is case-insensitive.
     * @param string the property name
     * @return boolean whether the property can be read
     * @see canSetProperty
     */
    public function canGetProperty($name)
    {
        return method_exists($this,'get'.$name);
    }

    /**
     * Determines whether a property can be set.
     * A property can be written if the class has a setter method
     * for the property name. Note, property name is case-insensitive.
     * @param string the property name
     * @return boolean whether the property can be written
     * @see canGetProperty
     */
    public function canSetProperty($name)
    {
        return method_exists($this,'set'.$name);
    }

}
